/*
 * dirent.h - dirent API for Microsoft Visual Studio
 * 
 * Copyright (C) 2006 Toni Ronkko
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * ``Software''), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 *
 * Oct 10, 2012, Boris Mikic
 * Added support for WinRT.
 * Edited for improved support within hltypes.
 *
 * Jan 18, 2008, Toni Ronkko
 * Using FindFirstFileA and WIN32_FIND_DATAA to avoid converting string
 * between multi-byte and unicode representations.  This makes the
 * code simpler and also allows the code to be compiled under MingW.  Thanks
 * to Azriel Fasten for the suggestion.
 *
 * Mar 4, 2007, Toni Ronkko
 * Bug fix: due to the strncpy_s() function this file only compiled in
 * Visual Studio 2005.  Using the new string functions only when the
 * compiler version allows.
 * 
 * Nov  2, 2006, Toni Ronkko
 * Major update: removed support for Watcom C, MS-DOS and Turbo C to
 * simplify the file, updated the code to compile cleanly on Visual
 * Studio 2005 with both unicode and multi-byte character strings,
 * removed rewinddir() as it had a bug.
 * 
 * Aug 20, 2006, Toni Ronkko
 * Removed all remarks about MSVC 1.0, which is antiqued now.  Simplified
 * comments by removing SGML tags.
 *
 * May 14 2002, Toni Ronkko
 * Embedded the function definitions directly to the header so that no
 * source modules need to be included in the Visual Studio project.  Removed
 * all the dependencies to other projects so that this very header can be
 * used independently.
 *
 * May 28 1998, Toni Ronkko
 * First version.
 */

#ifndef DIRENT_H
#define DIRENT_H

#include <windows.h>
#include <string.h>
#include <assert.h>

#include "hplatform.h"
#include "hstring.h"

typedef struct dirent
{
	// name of current directory entry (a multi-byte character string)
	wchar_t d_name[MAX_PATH + 1];
	// file attributes
	WIN32_FIND_DATAW data;
} dirent;

typedef struct DIR
{
	// current directory entry
	dirent current;
	// is there an un-processed entry in current?
	int cached;
	// file search handle
	HANDLE search_handle;
	// search pattern (3 = zero terminator + pattern "\\*")
	wchar_t pattern[MAX_PATH + 3];
} DIR;

static DIR* _opendir(chstr dirname);
static dirent* _readdir(DIR *dirp);
static int _closedir(DIR* dirp);

// use the new safe string functions introduced in Visual Studio 2005
#if defined(_MSC_VER) && _MSC_VER >= 1400
#define WCSNCPY(dest, src, size) wcsncpy_s((dest), (size), (src), _TRUNCATE)
#else
#define WCSNCPY(dest, src, size) wcsncpy((dest), (src), (size))
#endif

/*
 * Open directory stream DIRNAME for read and return a pointer to the
 * internal working area that is used to retrieve individual directory
 * entries.
 */
static DIR* _opendir(chstr dirname)
{
	if (dirname == "" || dirname.size() >= MAX_PATH)
	{
		return NULL;
	}
	// construct new DIR structure
	DIR* dirp = new DIR();
	if (dirp != NULL)
	{
		// take directory name...
		WCSNCPY(dirp->pattern, dirname.wStr().c_str(), MAX_PATH);
		dirp->pattern[MAX_PATH] = '\0';
		// ... and append search pattern to it
		wchar_t* p = wcschr(dirp->pattern, '\0');
		if (p == NULL)
		{
			p = dirp->pattern;
		}
		if (dirp->pattern < p && *(p - 1) != '/' && *(p - 1) != ':')
		{
			*p++ = '/';
		}
		*p++ = '*';
		*p = '\0';
		// open stream and retrieve first file
#ifndef _WINRT
		dirp->search_handle = FindFirstFileW(dirp->pattern, &dirp->current.data);
#else
		dirp->search_handle = FindFirstFileEx(dirp->pattern, FindExInfoBasic,
			&dirp->current.data, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
#endif
		if (dirp->search_handle == INVALID_HANDLE_VALUE)
		{
			// invalid search pattern?
			delete dirp;
			return NULL;
		}
		// there is an un-processed directory entry in memory now
		dirp->cached = 1;
	}
	return dirp;
}
  
/*
 * Read a directory entry, and return a pointer to a dirent structure
 * containing the name of the entry in d_name field.  Individual directory
 * entries returned by this very function include regular files,
 * sub-directories, pseudo-directories "." and "..", but also volume labels,
 * hidden files and system files may be returned.  
 */
static dirent* _readdir(DIR* dirp)
{
	if (dirp == NULL)
	{
		return NULL;
	}
	if (dirp->search_handle == INVALID_HANDLE_VALUE)
	{
		// directory stream was opened/rewound incorrectly or it ended normally
		return NULL;
	}
	// get next directory entry
	if (dirp->cached != 0)
	{
		// a valid directory entry already in memory
		dirp->cached = 0;
	}
	else
	{
		// read next directory entry from disk
		if (FindNextFileW(dirp->search_handle, &dirp->current.data) == FALSE)
		{
			// the very last file has been processed or an error occured
			FindClose(dirp->search_handle);
			dirp->search_handle = INVALID_HANDLE_VALUE;
			return NULL;
		}
	}
	// copy as a multibyte character string
	WCSNCPY(dirp->current.d_name, dirp->current.data.cFileName, (MAX_PATH + 2) * sizeof(wchar_t));
	dirp->current.d_name[MAX_PATH] = '\0';
	return &dirp->current;
}

/*
 * Close directory stream opened by opendir() function.  Close of the
 * directory stream invalidates the DIR structure as well as any previously
 * read directory entry.
 */
static int _closedir(DIR *dirp)
{
	assert(dirp != NULL);
	// release search handle
	if (dirp->search_handle != INVALID_HANDLE_VALUE)
	{
		FindClose(dirp->search_handle);
		dirp->search_handle = INVALID_HANDLE_VALUE;
	}
	// release directory handle
	delete dirp;
	return 0;
}

#endif
